﻿/*
** Haaf's Game Engine 1.5
** Copyright (C) 2003-2004, Relish Games
** hge.relishgames.com
**
** hgeParticleSystem helper class header
*/


module core.hgeparticle;

private import core.hgeall;
private import std.windows.charset;
private import std.c.math;



const MAX_PARTICLES	=500;
const MAX_PSYSTEMS		=100;

struct hgeParticle {
    hgeVector	vecLocation;
    hgeVector	vecVelocity;

    float		fGravity;
    float		fRadialAccel;
    float		fTangentialAccel;

    float		fSpin;
    float		fSpinDelta;

    float		fSize;
    float		fSizeDelta;

    hgeColor	colColor;		// + alpha
    hgeColor	colColorDelta;

    float		fAge;
    float		fTerminalAge;
};

struct hgeParticleSystemInfo {
    hgeSprite	sprite;    // texture + blend mode
    int			nEmission; // particles per sec
    float		fLifetime;

    float		fParticleLifeMin;
    float		fParticleLifeMax;

    float		fDirection;
    float		fSpread;
    bool		bRelative;

    float		fSpeedMin;
    float		fSpeedMax;

    float		fGravityMin;
    float		fGravityMax;

    float		fRadialAccelMin;
    float		fRadialAccelMax;

    float		fTangentialAccelMin;
    float		fTangentialAccelMax;

    float		fSizeStart;
    float		fSizeEnd;
    float		fSizeVar;

    float		fSpinStart;
    float		fSpinEnd;
    float		fSpinVar;

    hgeColor	colColorStart; // + alpha
    hgeColor	colColorEnd;
    float		fColorVar;
    float		fAlphaVar;
};

class parException: Exception {
    this(char[] msg) {
        super(	"	*载入粒子文件失败!	名称= "~msg);
    }
}

class hgeParticleSystem {
public:
    hgeParticleSystemInfo info;

    this(char[] filename, hgeSprite sprite, float fps=0.0f) {
        if (!hge)hge = gameInfo.hge();

		void[] psi= std.file.read(filename);
		info= *(cast(hgeParticleSystemInfo*)psi);
		delete psi;
		mes("par--------	nE= ", info.nEmission, "	der=",info.fDirection);
/*         psi=hge.Resource_Load(toMBSz(filename));
        if (!psi) {
            throw new Exception(filename);
        }
        memcpy(&info, psi, hgeParticleSystemInfo.sizeof);
        hge.Resource_Free(psi); */
        info.sprite=sprite;
	vecLocation.x=vecPrevLocation.x=0.0f;
	vecLocation.y=vecPrevLocation.y=0.0f;
        //vecLocation= new hgeVector;
        //vecPrevLocation=new hgeVector;	//vecLocation.x=0.0f;	vecPrevLocation.x=0.0f;
        //	vecLocation		=hgeVector();

        //	vecPrevLocation	=hgeVector();
        fTx=fTy=0;

        fEmissionResidue=0.0f;
        nParticlesAlive=0;
        fAge=-2.0;
        if (fps!=0.0f) fUpdSpeed=1.0f/fps;
        else fUpdSpeed=0.0f;
        fResidue=0.0f;

        rectBoundingBox.Clear();
        bUpdateBoundingBox=false;
    }



    this(hgeParticleSystemInfo psi, float fps=0.0f) {
        if (!hge)hge = gameInfo.hge();

        memcpy(cast(void*)&info, cast(void*)&psi, hgeParticleSystemInfo.sizeof);

        vecLocation.x=vecPrevLocation.x=0.0f;
        vecLocation.y=vecPrevLocation.y=0.0f;
        fTx=fTy=0;

        fEmissionResidue=0.0f;
        nParticlesAlive=0;
        fAge=-2.0;
        if (fps!=0.0f) fUpdSpeed=1.0f/fps;
        else fUpdSpeed=0.0f;
        fResidue=0.0f;

        rectBoundingBox.Clear();
        bUpdateBoundingBox=false;
    }


    this(inout hgeParticleSystem	ps) {
        memcpy(cast(void*)this, cast(void*)&ps, hgeParticleSystem.sizeof);
        if (!hge)hge = gameInfo.hge();
    }


//	~this() { hge.Release(); }



//	hgeParticleSystem&	operator= ( hgeParticleSystem &ps);



    void Render() {
        int i;
        uint col;
        hgeParticle[] par=particles;

        col=info.sprite.GetColor();

        for (i=0; i<nParticlesAlive; i++) {
            info.sprite.SetColor(par[i].colColor.GetHWColor());
            info.sprite.RenderEx(par[i].vecLocation.x+fTx, par[i].vecLocation.y+fTy, par[i].fSpin*par[i].fAge, par[i].fSize);
        }

        info.sprite.SetColor(col);
    }

    void	FireAt(float x, float y) {
        Stop();
        MoveTo(x,y);
        Fire();
    }

    void 	Fire() {
        if (info.fLifetime==-1.0f) fAge=-1.0f;
        else fAge=0.0f;
        fResidue=0.0;
    }

    void 	Stop(bool bKillParticles=false) {
        fAge=-2.0f;
        if (bKillParticles) {
            nParticlesAlive=0;
            rectBoundingBox.Clear();
        }
    }


    void 	Update(float fDeltaTime) {
        if (fUpdSpeed==0.0f) { //mes("ccccc=",fDeltaTime);
            _update(fDeltaTime);
        } else {
            fResidue+=fDeltaTime;
            if (fResidue>=fUpdSpeed) {
                _update(fUpdSpeed);
                while (fResidue>=fUpdSpeed) fResidue-=fUpdSpeed;
            }
        }
    }

    void  _update(float fDeltaTime) {
        int i;
        float ang;
        hgeParticle[] par;
        hgeVector vecAccel, vecAccel2;

        if (fAge >= 0) {
            fAge += fDeltaTime;
            if (fAge >= info.fLifetime) fAge = -2.0f;
        }

        // update all alive particles

        if (bUpdateBoundingBox) rectBoundingBox.Clear();

        par=particles;

        for (i=0; i<nParticlesAlive; i++) {
            par[i].fAge += fDeltaTime;
            if (par[i].fAge >= par[i].fTerminalAge) {
                nParticlesAlive--;
               // memcpy(par.ptr, cast(void*)&particles[nParticlesAlive], hgeParticle.sizeof);
				par[i]=particles[nParticlesAlive];
				i--;
                continue;
            }

            vecAccel = par[i].vecLocation-vecLocation;
            vecAccel.Normalize();
            vecAccel2 = vecAccel;
            vecAccel *= par[i].fRadialAccel;

            // vecAccel2.Rotate(M_PI_2);
            // the following is faster
            ang = vecAccel2.x;
            vecAccel2.x = -vecAccel2.y;
            vecAccel2.y = ang;

            vecAccel2 *= par[i].fTangentialAccel;
            par[i].vecVelocity += (vecAccel+vecAccel2)*fDeltaTime;
            par[i].vecVelocity.y += par[i].fGravity*fDeltaTime;

            par[i].vecLocation += par[i].vecVelocity;

            par[i].fSpin += par[i].fSpinDelta*fDeltaTime;
            par[i].fSize += par[i].fSizeDelta*fDeltaTime;
            par[i].colColor += par[i].colColorDelta*fDeltaTime;

            if (bUpdateBoundingBox) rectBoundingBox.Encapsulate(par[i].vecLocation.x, par[i].vecLocation.y);

        }

        // generate new particles

        if (fAge != -2.0f) {
            float fParticlesNeeded = info.nEmission*fDeltaTime + fEmissionResidue;
            int nParticlesCreated = cast(uint)fParticlesNeeded;
            fEmissionResidue=fParticlesNeeded-nParticlesCreated;

            int k=nParticlesAlive;
            //par=&particles[nParticlesAlive];
//mes(nParticlesCreated);
            for (i=0; i<nParticlesCreated; i++) {
                if (nParticlesAlive>=MAX_PARTICLES) break;

                par[k].fAge = 0.0f;
                par[k].fTerminalAge = hge.Random_Float(info.fParticleLifeMin, info.fParticleLifeMax);

                par[k].vecLocation = vecPrevLocation +	(vecLocation-vecPrevLocation) *	hge.Random_Float(0.0f, 1.0f);

                par[k].vecLocation.x += hge.Random_Float(-2.0f, 2.0f);
                par[k].vecLocation.y += hge.Random_Float(-2.0f, 2.0f);

                ang=info.fDirection-M_PI_2+hge.Random_Float(0,info.fSpread)-info.fSpread/2.0f;
                if (info.bRelative) ang += (vecPrevLocation-vecLocation).Angle()+M_PI_2;
                par[k].vecVelocity.x = cosf(ang);
                par[k].vecVelocity.y = sinf(ang);
                par[k].vecVelocity *= hge.Random_Float(info.fSpeedMin, info.fSpeedMax);

                par[k].fGravity = hge.Random_Float(info.fGravityMin, info.fGravityMax);
                par[k].fRadialAccel = hge.Random_Float(info.fRadialAccelMin, info.fRadialAccelMax);
                par[k].fTangentialAccel = hge.Random_Float(info.fTangentialAccelMin, info.fTangentialAccelMax);

                par[k].fSize = hge.Random_Float(info.fSizeStart, info.fSizeStart+(info.fSizeEnd-info.fSizeStart)*info.fSizeVar);
                par[k].fSizeDelta = (info.fSizeEnd-par[k].fSize) / par[k].fTerminalAge;
                par[k].fSpin = hge.Random_Float(info.fSpinStart, info.fSpinStart+(info.fSpinEnd-info.fSpinStart)*info.fSpinVar);
                par[k].fSpinDelta = (info.fSpinEnd-par[k].fSpin) / par[k].fTerminalAge;

                par[k].colColor.r = hge.Random_Float(info.colColorStart.r, info.colColorStart.r+(info.colColorEnd.r-info.colColorStart.r)*info.fColorVar);
                par[k].colColor.g = hge.Random_Float(info.colColorStart.g, info.colColorStart.g+(info.colColorEnd.g-info.colColorStart.g)*info.fColorVar);
                par[k].colColor.b = hge.Random_Float(info.colColorStart.b, info.colColorStart.b+(info.colColorEnd.b-info.colColorStart.b)*info.fColorVar);
                par[k].colColor.a = hge.Random_Float(info.colColorStart.a, info.colColorStart.a+(info.colColorEnd.a-info.colColorStart.a)*info.fAlphaVar);
                par[k].colColorDelta.r = (info.colColorEnd.r-par[k].colColor.r) / par[k].fTerminalAge;
                par[k].colColorDelta.g = (info.colColorEnd.g-par[k].colColor.g) / par[k].fTerminalAge;
                par[k].colColorDelta.b = (info.colColorEnd.b-par[k].colColor.b) / par[k].fTerminalAge;
                par[k].colColorDelta.a = (info.colColorEnd.a-par[k].colColor.a) / par[k].fTerminalAge;

                if (bUpdateBoundingBox) rectBoundingBox.Encapsulate(par[k].vecLocation.x, par[k].vecLocation.y);

                nParticlesAlive++;
                k++;
            }
        }

        vecPrevLocation=vecLocation;
    }







    void 	MoveTo(float x, float y, bool bMoveParticles=false) {
        int i;
        float dx,dy;

        if (bMoveParticles) {
            dx=x-vecLocation.x;
            dy=y-vecLocation.y;

            for (i=0;i<nParticlesAlive;i++) {
                particles[i].vecLocation.x += dx;
                particles[i].vecLocation.y += dy;
            }

            vecPrevLocation.x=vecPrevLocation.x + dx;
            vecPrevLocation.y=vecPrevLocation.y + dy;
        } else {
            if (fAge==-2.0) {
                vecPrevLocation.x=x;
                vecPrevLocation.y=y;
            } else {
                vecPrevLocation.x=vecLocation.x;
                vecPrevLocation.y=vecLocation.y;
            }
        }

        vecLocation.x=x;
        vecLocation.y=y;
    }




    void	Transpose(float x, float y) {
        fTx=x;
        fTy=y;
    }
    void	TrackBoundingBox(bool bTrack) {
        bUpdateBoundingBox=bTrack;
    }

    int		GetParticlesAlive()  {
        return nParticlesAlive;
    }
    float	GetAge()  {
        return fAge;
    }
    void	GetPosition(float *x, float *y)  {
        *x=vecLocation.x;
        *y=vecLocation.y;
    }
    void	GetTransposition(float *x, float *y)  {
        *x=fTx;
        *y=fTy;
    }
    hgeRect* GetBoundingBox(hgeRect *rect)  {
        memcpy(rect, &rectBoundingBox, hgeRect.sizeof);
        return rect;
    }

private:
    this() {		};
//	void				_update(float fDeltaTime);

    static ifHge		hge;

    float				fUpdSpeed;
    float				fResidue;

    float				fAge;
    float				fEmissionResidue;

    hgeVector			vecPrevLocation;
    hgeVector			vecLocation;
    float				fTx, fTy;

    int					nParticlesAlive;
    hgeRect				rectBoundingBox;
    bool				bUpdateBoundingBox;

    hgeParticle[MAX_PARTICLES]			particles;
}

class hgeParticleManager {
public:
    this(float fps=0.0f) {
        nPS=0;
        fFPS=fps;
        tX=tY=0.0f;
    }

    ~this() {
        int i;
        for (i=0;i<nPS;i++) delete psList[i];
    }



    void				Update(float dt) {
        int i;
        for (i=0;i<nPS;i++) {
            psList[i].Update(dt);
            if (psList[i].GetAge()==-2.0f && psList[i].GetParticlesAlive()==0) {
                delete psList[i];
                psList[i]=psList[nPS-1];
                nPS--;
                i--;
            }
        }
    }



    void				Render() {
        int i;
        for (i=0;i<nPS;i++) psList[i].Render();
    }





    hgeParticleSystem	SpawnPS(hgeParticleSystemInfo psi, float x, float y) {
        if (nPS==MAX_PSYSTEMS) return null;
        psList[nPS]=new hgeParticleSystem(psi,fFPS);
        psList[nPS].FireAt(x,y);
        psList[nPS].Transpose(tX,tY);
        nPS++;
        return psList[nPS-1];
    }




    bool				IsPSAlive(hgeParticleSystem ps) {
        int i;
        for (i=0;i<nPS;i++) if (psList[i]==ps) return true;
        return false;
    }



    void				Transpose(float x, float y) {
        int i;
        for (i=0;i<nPS;i++) psList[i].Transpose(x,y);
        tX=x;
        tY=y;
    }



    void				GetTransposition(float *dx, float *dy)  {
        *dx=tX;
        *dy=tY;
    }
    void				KillPS(hgeParticleSystem 	ps) {
        int i;
        for (i=0;i<nPS;i++) {
            if (psList[i]==ps) {
                delete psList[i];
                psList[i]=psList[nPS-1];
                nPS--;
                return;
            }
        }
    }



    void		KillAll() {
        int i;
        for (i=0;i<nPS;i++) delete psList[i];
        nPS=0;
    }

private:
this(hgeParticleManager a) {		};
//	hgeParticleManager&	operator= ( hgeParticleManager ){		};

    float				fFPS;
    int					nPS;
    float				tX;
    float				tY;
    hgeParticleSystem[MAX_PSYSTEMS]		psList;
}
